/**
 * @brief 串口接口模块，主要用来实现和串口的数据通讯操作。
 * @note 这个使用了移远提供部分API函数来操作串口，因此程序的通用性不高
 * @version 1.0
 * @date 2018-2-25
 * @author 宋炜
*/
#ifndef __SERIAL_HPP__
#define __SERIAL_HPP__

#include <string>
#include <functional>
#include <thread>
#include <mutex>
#include "config.hpp"
#if USE_QUEC_UART == 0
#include "uv.h"
#endif
#include "defines.hpp"
#include "timer.hpp"

#if defined( __USE_MEMPOOL__ )
    #include <jemalloc/jemalloc.h>
#endif

class serial
{
#if USE_QUEC_UART == 1

#else
    	friend void on_read_file( uv_fs_t * req );
#endif
public:
	struct stop{ enum value { NONE = 0, ONE , ONE_P_FIVE , TWO }; };
	struct parity{ enum value {NONE = 0 , EVEN = 1, ODD = 2};};
	struct flow{enum value{NONE = 0, HARD , SOFT, KEEPBACK };};
	
	enum err_code{
		ERR_BASE  = ERR_SERIAL,
		ERR_UV_LOOP_INIT ,         // 初始化libuv消息循环失败
		ERR_ALLOC_MEM,             // 分配内存失败
		ERR_DEV_OCCUPIED ,         // 串口被占用
		ERR_DEV_NOT_EXIST,         // 串口不存在
		ERR_SEND_DATA_EMPTY,       // 发送数据空
		ERR_SET_PARAMS,            // 设置串口参数失败
		ERR_BAD_DEV,               // 串口号错误
		ERR_BAD_FD,                // 系统错误文件描述符
		ERR_OPEN_SERIAL,           // 打开串口错误
		ERR_CLOSE_UART,            // 关闭串口出错
		ERR_SEND_FAIL,
		OK = 0
	};
#if USE_QUEC_UART == 1 
	class buffDeleter
	{
	public:
		buffDeleter(){}
		template< class T >
		void operator()( T * p ){
			if( p ) free( p );
		}
	};
#else
	struct writeHandle{
		serial      * obj;
		uv_buf_t      buf;
		size_t        len;
	};
#endif
protected:
    
	/**
	 * libuv 没有提供串口操作的功能，但是在linux下所有的设备都是可以看做文件来处理的
	 * 使用libuv提供的文件操作功能打开串口文件，然后获取标准的文件描述符调用linux
	 *      #include<termios.h>
	 *      int tcsetattr(int fd , int actions , const struct termios *termios_h);
	 * 设置串口参数。此后所有的异步操作均采用libuv操作。
	 */
#if USE_QUEC_UART == 1
	std::atomic< int >            m_fd;           // 串口文件句柄
	std::shared_ptr< char >       m_buff;         // 接受数据的缓冲区
#else
	uv_loop_t                  * p_uv_loop;      // 使用libuv作为异步操作驱动

	uv_fs_t                      m_f_serial;     // 串口文件句柄
	uv_fs_t                      m_f_read;       // 读取请求
	uv_fs_t                      m_f_write;      // 写请求
	uv_file                      m_fh;           // 文件句柄
	uv_buf_t                     m_buff;         // 数据缓冲区
	uv_buf_t                     m_o_buff;       // 发送缓冲区
#endif
	std::string                  m_name;         // 串口名称

	int                          m_baud;         // 波特率
	int                          m_char_len;     // 字符长度
	stop::value                  m_stop;         // 停止位
	parity::value                m_parity;       // 校验位
	flow::value                  m_flow;         // 流控

	std::atomic<err_code>        m_error;        // 错误编号
	std::atomic<bool>            m_is_open;      // 当前状态
	std::atomic<bool>            m_is_run;
	std::atomic<size_t >         m_buff_size;    // 缓冲区大小
	std::mutex                   m_mutex;        //
	/*
	* 接收数据的回调函数定义：
	*
	*  void fun( size_t len , const char* data , err_code err );
	*  参数：
	*    len , 接收到的数据长度
	*    data ， 接收到的数据缓冲指针
	*    err , 操作错误代码，成功操作为OK ，否则为错误代码
	*/
	std::function< void (size_t , const char* , err_code)> on_recv;   // 接收到数据后回调
	/*
	* 发送完成回调函数定义：
	*/
	std::function< void (size_t , err_code )             > on_send;   // 发送操作回调函数
	/*
	* 串口打开回调函数定义：
	*
	*/
	std::function< void (err_code)                       > on_open;   // 响应打开串口操作
	/*
	* 串口关闭回调函数定义：
	*/
	std::function< void (err_code)                       > on_close;  // 响应关闭操作

public:
	/**
	 * @brief 创建对象，设置窗口名称和接收到数据的时候回调响应
	 * @param name
	 * @exception 如果缓冲区内存分配失败，会抛出ERR_ALLOC_MEM
	 */
	serial( const std::string& name );
	/**
	 * @brief 创建并打开串口
	 * @param baud 波特率
	 * @param char_len 字长
	 * @param s 停止位
	 * @param p 校验位
	 * @param f 流控参数
	 * @exception 如果缓冲区内存分配失败，会抛出ERR_ALLOC_MEM
	 */
	serial( const std::string& name ,
		int baud ,
		int char_len ,
		stop::value s ,
		parity::value p ,
		flow::value f
	);

	virtual ~serial();
	/**
	 * @brief 打开串口。
	 * @param baud 波特率
	 * @param char_len 字符长度
	 * @param s 停止位
	 * @param p 校验位
	 * @param f 流控
	 * @retval 成功返回OK, 失败返回错误代码
	 */
	err_code open( int baud , int char_len , stop::value s , parity::value p , flow::value f );
	err_code close();
	/**
	 * @brief 发送数据。这个函数调用后立即返回，操作结果从回调中获取，因此这个函数执行返回后
	 *        并不一位置发送完成
	 * @param len ， 要发送的数据长度
	 * @param data , 要发送的数据
	 */
	err_code send( size_t len , const char* data );
	/**
	 * @brief 操作事件设置操作事件
	 */
	serial& evtOpen(  std::function< void (err_code)> fun                        );
	serial& evtClose( std::function< void (err_code)> fun                        );
	serial& evtSend(  std::function< void (size_t , err_code)> fun               );
	serial& evtRecv(  std::function< void (size_t , const char* , err_code)> fun );
	/**
	 * @brief 复位串口。关闭串口重新打开
	 */
	err_code reset();
	err_code reset( int baud , int char_len , stop::value s , parity::value p , flow::value f );
	/**
	 * @brief 串口是否打开
	 * @return 打开返回true , 否则返回false
	 */
	bool is_open(){ return m_is_open.load();}
	/**
	 * @brief 启动串口循环
	 */
	void run();
	/**
	 * @brief 结束串口消息循环
	 */
	void standby();
	/**
	 * @brief 响应libuv 打开文件操作
	 * @param fd , uv_file句柄
	 */
	void OnOpen( int fd );
	/**
	 * @brief 响应读取数据完成
	 * @param req
	 * @param len
	 */
#if USE_QUEC_UART == 1
    	void OnRecv( const char * data , size_t len );
#else
    	void OnRecv( uv_fs_t * req , size_t len);
#endif
	/**
	 * @brief 响应写数据完成
	 * @param len , 已经发送的数据长度
	 * @param e , 操作过程出现的错误代码
	 */
	void OnSend( size_t len , err_code e );
	/**
	 * @brief 响应串口关闭
	 */
	void OnClose( err_code e );
	/**
	 * @brief 更新串口参数，这个函数主要的目的是方便RFC2217.
	 */
	static bool updt_param( int fd , int baud , int char_len , stop::value s , parity::value p , flow::value f );
	/**
	 * @brief 设置缓冲大小
	 * @param l
	 * @exception 如果内存分配失败抛出ERR_ALLOC_MEM
	 */
	void SetBufferSize( size_t l );
protected:
	/**
	 * @brief 初始化设备参数，试着波特率这些内容
	 * @param fd , 注意这个fd是linux下的文件描述符
	 * @param baud
	 * @param char_len
	 * @param s
	 * @param p
	 * @param f
	 */
	void init_device( int fd , int baud , int char_len , stop::value s , parity::value p , flow::value f );
#if USE_QUEC_UART == 1
    
#else
    	void __init_uv_loop();
#endif
};
#endif // __SERIAL_HPP__
